﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

using Nest;
using Newtonsoft.Json;
using ES.Client.Utility;

namespace ES.Client.Filter
{
    public class QueryFilter<T> where T : class
    {
        internal QueryFilter(string index, string docType)
        {
            this.Index = index;
            this.DocType = docType;
            this.Containers = new List<QueryContainer>();
        }

        private string Index;

        private string DocType;

        private List<QueryContainer> Containers;

        static object _lock = new object();
        private BuilderFactory<T> factory
        {
            get
            {
                var key = this.Index + "_" + this.DocType + "_factory";
                var _factory = Cache.Get(key) as BuilderFactory<T>;
                if (_factory == null)
                {
                    lock (_lock)
                    {
                        _factory = Cache.Get(key) as BuilderFactory<T>;
                        if (_factory == null)
                        {
                            _factory = new BuilderFactory<T>(this.Index, this.DocType);
                            Cache.Set(key, _factory, 30);
                        }
                    }
                }

                return _factory;
            }
        }

        public QueryFilter<T> EQ<TValue>(string field, TValue value)
        {
            this.Containers.AddRange(factory.GetBuilder(field).EQ(field, value));
            return this;
        }

        public QueryFilter<T> EQ<TValue>(Expression<Func<T, object>> expression, TValue value)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).EQ(field, value));
            return this;
        }

        public QueryFilter<T> NE<TValue>(string field, TValue value)
        {
            this.Containers.AddRange(factory.GetBuilder(field).NE(field, value));
            return this;
        }

        public QueryFilter<T> NE<TValue>(Expression<Func<T, object>> expression, TValue value)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).NE(field, value));
            return this;
        }

        public QueryFilter<T> GT<TValue>(string field, TValue value)
        {
            this.Containers.AddRange(factory.GetBuilder(field).GT(field, value));
            return this;
        }

        public QueryFilter<T> GT<TValue>(Expression<Func<T, object>> expression, TValue value)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).GT(field, value));
            return this;
        }

        public QueryFilter<T> GTE<TValue>(string field, TValue value)
        {
            this.Containers.AddRange(factory.GetBuilder(field).GTE(field, value));
            return this;
        }

        public QueryFilter<T> GTE<TValue>(Expression<Func<T, object>> expression, TValue value)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).GTE(field, value));
            return this;
        }

        public QueryFilter<T> LT<TValue>(string field, TValue value)
        {
            this.Containers.AddRange(factory.GetBuilder(field).LT(field, value));
            return this;
        }

        public QueryFilter<T> LT<TValue>(Expression<Func<T, object>> expression, TValue value)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).LT(field, value));
            return this;
        }

        public QueryFilter<T> LTE<TValue>(string field, TValue value)
        {
            this.Containers.AddRange(factory.GetBuilder(field).LTE(field, value));
            return this;
        }

        public QueryFilter<T> LTE<TValue>(Expression<Func<T, object>> expression, TValue value)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).LTE(field, value));
            return this;
        }

        /// <summary>
        /// 判断文档是否包含某个字段，字段值既不等于null也不等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public QueryFilter<T> Exists(string field)
        {
            this.Containers.AddRange(factory.GetBuilder(field).Exists(field));
            return this;
        }

        /// <summary>
        /// 判断文档是否包含某个字段，字段值既不等于null也不等于空字符串
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public QueryFilter<T> Exists(Expression<Func<T, object>> expression)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).Exists(field));
            return this;
        }

        /// <summary>
        /// 通过给定字段获取文档，字段值既不等于null也不等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public QueryFilter<T> IsNotNullAndEmpty(string field)
        {
            this.Containers.AddRange(factory.GetBuilder(field).IsNotNullAndEmpty(field));
            return this;
        }

        /// <summary>
        /// 通过给定字段获取文档，字段值既不等于null也不等于空字符串
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public QueryFilter<T> IsNotNullAndEmpty(Expression<Func<T, object>> expression)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).IsNotNullAndEmpty(field));
            return this;
        }

        /// <summary>
        /// 判断文档是否不包含某个文档，字段值等于null或者等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public QueryFilter<T> Missing(string field)
        {
            this.Containers.AddRange(factory.GetBuilder(field).Missing(field));
            return this;
        }

        /// <summary>
        /// 判断文档是否不包含某个文档，字段值等于null或者等于空字符串
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public QueryFilter<T> Missing(Expression<Func<T, object>> expression)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).Missing(field));
            return this;
        }

        /// <summary>
        /// 通过给定字段获取文档，字段值等于null或者等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public QueryFilter<T> IsNullOrEmpty(string field)
        {
            this.Containers.AddRange(factory.GetBuilder(field).IsNullOrEmpty(field));
            return this;
        }

        /// <summary>
        /// 通过给定字段获取文档，字段值等于null或者等于空字符串
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public QueryFilter<T> IsNullOrEmpty(Expression<Func<T, object>> expression)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).IsNullOrEmpty(field));
            return this;
        }

        /// <summary>
        /// 以特定前缀搜索
        /// </summary>
        /// <param name="field"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public QueryFilter<T> Prefix(string field, string query)
        {
            this.Containers.AddRange(factory.GetBuilder(field).Prefix(field, query));
            return this;
        }

        /// <summary>
        /// 以特定前缀搜索
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public QueryFilter<T> Prefix(Expression<Func<T, object>> expression, string query)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).Prefix(field, query));
            return this;
        }

        /// <summary>
        /// 指定字段模糊搜索
        /// </summary>
        /// <param name="field"></param>
        /// <param name="keyword"></param>
        /// <returns></returns>
        public QueryFilter<T> Like(string field, string keyword)
        {
            this.Containers.AddRange(factory.GetBuilder(field).Like(field, keyword));
            return this;
        }

        /// <summary>
        /// 指定字段模糊搜索
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="keyword"></param>
        /// <returns></returns>
        public QueryFilter<T> Like(Expression<Func<T, object>> expression, string keyword)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).Like(field, keyword));
            return this;
        }

        /// <summary>
        /// 全文搜索，基于_all字段
        /// </summary>
        /// <param name="keyword"></param>
        /// <returns></returns>
        public QueryFilter<T> FullText(string keyword)
        {
            this.Containers.AddRange(factory.GetBuilder(BuilderType.BasicField).Like("_all", keyword));
            return this;
        }

        /// <summary>
        /// 正则表达式搜索
        /// </summary>
        /// <param name="field"></param>
        /// <param name="regex"></param>
        /// <returns></returns>
        public QueryFilter<T> Regex(string field, string regex)
        {
            this.Containers.AddRange(factory.GetBuilder(field).Regex(field, regex));
            return this;
        }

        /// <summary>
        /// 正则表达式搜索
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="regex"></param>
        /// <returns></returns>
        public QueryFilter<T> Regex(Expression<Func<T, object>> expression, string regex)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).Regex(field, regex));
            return this;
        }

        /// <summary>
        /// IN查询
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="field"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public QueryFilter<T> IN<TValue>(string field, IEnumerable<TValue> values)
        {
            this.Containers.AddRange(factory.GetBuilder(field).IN(field, values));
            return this;
        }

        /// <summary>
        /// IN查询
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="expression"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public QueryFilter<T> IN<TValue>(Expression<Func<T, object>> expression, IEnumerable<TValue> values)
        {
            var field = FieldUtil.GetFieldName(expression);
            this.Containers.AddRange(factory.GetBuilder(field).IN(field, values));
            return this;
        }

        /// <summary>
        /// 自定义查询条件
        /// </summary>
        /// <param name="container"></param>
        /// <returns></returns>
        public QueryFilter<T> Custom(QueryContainer container)
        {
            this.Containers.Add(container);
            return this;
        }

        /// <summary>
        /// OR连接
        /// </summary>
        /// <param name="containers"></param>
        /// <returns></returns>
        public QueryFilter<T> OR(params QueryContainer[] containers)
        {
            this.Containers.Add(Nest.Query<T>.Bool(b => b.Should(containers)));
            return this;
        }

        /// <summary>
        /// AND连接
        /// </summary>
        /// <param name="containers"></param>
        /// <returns></returns>
        public QueryFilter<T> AND(params QueryContainer[] containers)
        {
            this.Containers.Add(Nest.Query<T>.Bool(b => b.Must(containers)));
            return this;
        }

        public QueryContainer[] Build()
        {
            return this.Containers.ToArray();
        }
    }
}
